<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>JavaScript自定义对象</title>
		<link rel="stylesheet" type="text/css" href="css/reset.css"/>
		<link rel="stylesheet" type="text/css" href="css/common.css"/>
	</head>
	<body>
		<div class="web">
			<h1>JavaScript自定义对象</h1>
			<hr />
			<h2>关于对象的回顾</h2>
			<p>Javascript是一种面向对象的语言，因此可以使用面向对象的思想来进行
javascript程序设计</p>
			<p>
对象就是由一些彼此相关的属性和方法集合在一起而构成的一个数据实体。</p>
			<p><img src="img/对象示例.png"/ alt="对象中猫的图片"></p>
			<h2>对象的封装</h2>
			<details>
				<summary>对象直接量</summary>
				<pre>
					var obj = {
						name:"BGG",
						age:18,
						job:"教主"
					}
					//或者
					var obj2 = {};
					obj2.name = "BGG";
					obj2.age = 18;
					obj2.job = "教主";
				</pre>
				<p>缺点:</p>
				<p>一是如果多生成几个实例，写起来就非常麻烦；</p>
				<p>二是实例与原型之间，没有任何办法，可以看出有什么联系。</p>
			</details>
			<details>
				<summary>工厂模式</summary>
				<pre>
					　function person(name,age) {
					　　　　return {
					　　　　　　name:name,
					　　　　　　age:age
					　　　　}
　					　}
				var per1 = person("BGG",18);
				var per2 = person("wen",28);
				</pre>
				<p>写一个函数，解决代码重复的问题,生成实例对象，就等于是在调用函数</p>
				<p>这种方法的问题依然是，per1和per2之间没有内在的联系，不能反映出它们是同一个原型对象的实例。</p>
			</details>
			<details>
				<summary>构造函数模式</summary>
				<p>为了解决从原型对象生成实例的问题，Javascript提供了一个构造函数（Constructor）模式。</p>
				<p>所谓"构造函数"，其实就是一个普通函数，但是内部使用了this变量。对构造函数使用new运算符，就能生成实例，并且this变量会绑定在实例对象上。</p>
				<pre>
					　function person(name,age) {
				　　　　　　this.name = name;
				　　　　　　this.age = age;
					　　　this.say = function(){
							console.log("我爱"+this.age+"岁时的"+this.name);
						}
　					　}
				var per1 = new person("BGG",18);
				var per2 = new person("wen",28);
				</pre>
				<p>注意:new关键字的使用,它改变了this指向</p>
				<p>与工厂方式相比，使用构造函数方式创建对象，<b>无需再函数内部重建创建对象</b>，而使用this指代。</p>
				<p>在构造函数模式中，通过new运算符调用构造函数function来创建具体对象的过程就叫实例化</p>
				<p>这时per1和per2会自动含有一个constructor属性，指向它们的构造函数。</p>
				<pre>
					alert(per1.constructor == person); //true
　　					alert(per2.constructor == person); //true
				</pre>
				<p>Javascript还提供了一个instanceof运算符，验证原型对象与实例对象之间的关系。</p>
				<pre>
					alert(per1 instanceof person); //true
　　					alert(per2 instanceof person); //true
				</pre>
				<p>构造函数方法很好用，但是存在一个浪费内存的问题。</p>
				<pre>
					　alert(per1.say == per2.say); //false
				</pre>
				<p>能不能让say()方法在内存中只生成一次，然后所有实例都指向那个内存地址呢？</p>
			</details>
			<details>
				<summary>原型模式</summary>
				<p>Javascript规定，每一个<b>构造函数</b>都有一个prototype属性，指向另一个对象。这个对象的所有属性和方法，都会被构造函数的实例继承。</p>
				<p>这意味着，我们可以把那些不变的属性和方法，直接定义在prototype对象上。</p>
				<pre>
					function person(){};
					person.prototype.name = "BGG";
					person.prototype.age = 18;
					person.prototype.job = "教主";
					person.prototype.say = function(){
							console.log("我爱"+this.age+"岁时的"+this.name);
						}
				</pre>
				<p>解决了构造函数模式的内存浪费问题</p>
				<p>但书写不方便,且难以批量生产</p>
				<p>与构造函数模式结合使用,取长补短,成为下面的终极模式!</p>
			</details>
			<details>
				<summary>混合模式</summary>
				<pre>
					function person(name,age) {
				　　　　　　this.name = name;
				　　　　　　this.age = age;
　					　}
					person.prototype.type = "蓝景人";
					person.prototype.say = function(){
							console.log("我爱"+this.age+"岁时的"+this.name);
						}
						
					alert(per1.say == per2.say); //true
				</pre>
				<p>这时所有实例的say()方法，其实都是同一个内存地址，指向prototype对象，因此就提高了运行效率。</p>
				<p>isPrototypeOf()这个方法用来判断，某个proptotype对象和某个实例之间的关系。</p>
				<pre>
					alert(per.prototype.isPrototypeOf(per1)); //true
　　					alert(per.prototype.isPrototypeOf(per2)); //true
				</pre>
				<p>hasOwnProperty()每个实例对象都有一个hasOwnProperty()方法，用来判断某一个属性到底是本地属性，还是继承自prototype对象的属性。</p>
				<pre>
					alert(per1.hasOwnProperty("name")); // true
　　					alert(per1.hasOwnProperty("type")); // false
				</pre>
				<p>in运算符可以用来判断，某个<b>实例</b>是否含有某个属性，不管是不是本地属性。</p>
				<pre>
					alert("name" in per1); // true
　　					alert("type" in per1); // true
				</pre>
				<p>in拓展:in运算符还可以用来遍历某个对象的所有属性。</p>
				<pre>
					for(var prop in per1) { 
						console.log("per1["+prop+"]="+per1[prop]); }
				</pre>
			</details>
		</div>
		
		<h1>对象知识拓展-构造函数的继承</h1>
		<hr />
		<p>例如:有一个动物的构造函数和一个猫的构造函数</p>
		<pre>
			function Animal(){
　　　　			this.species = "动物";
　　			}
		</pre>
		<pre>
			　function Cat(name,color){
			　　　　this.name = name;
			　　　　this.color = color;
			　　}
		</pre>
		<details>
			<summary>构造函数绑定</summary>
			<p>使用call或apply方法，将父对象的构造函数绑定在子对象上，即在子对象构造函数中加一行：</p>
			<pre>
				function Cat(name,color){
				　　　Animal.apply(this, arguments);
				　　　this.name = name;
				　　　this.color = color;
			　　	}
			　　	var cat1 = new Cat("大毛","黄色");
			　　	alert(cat1.species); // 动物
			</pre>
		</details>
		<details>
			<summary>prototype模式</summary>
			<p>如果"猫"的prototype对象，指向一个Animal的实例，那么所有"猫"的实例，就能继承Animal了。</p>
			<pre>
				Cat.prototype = new Animal();
			　　	Cat.prototype.constructor = Cat;
			　　	var cat1 = new Cat("大毛","黄色");
			　　	alert(cat1.species); // 动物
			</pre>
			<p>代码的第一行，我们将Cat的prototype对象指向一个Animal的实例。</p>
			<p>它相当于完全删除了prototype 对象原先的值，然后赋予一个新值。但是，第二行又是什么意思呢？</p>
			<p>原来，任何一个prototype对象都有一个constructor属性，指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行，Cat.prototype.constructor是指向Cat的；加了这一行以后，Cat.prototype.constructor指向Animal。</p>
			<pre>
				alert(Cat.prototype.constructor == Animal); //true
			</pre>
			<p>更重要的是，每一个实例也有一个constructor属性，默认调用prototype对象的constructor属性。</p>
			<pre>
				alert(cat1.constructor == Cat.prototype.constructor); // true
			</pre>
			<p>因此，在运行"Cat.prototype = new Animal();"这一行之后，cat1.constructor也指向Animal！</p>
			<pre>
				alert(cat1.constructor == Animal); // true
			</pre>
			<p>这显然会导致继承链的紊乱（cat1明明是用构造函数Cat生成的），因此我们必须手动纠正，将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。</p>
			<p>这是很重要的一点，编程时务必要遵守。下文都遵循这一点，即如果替换了prototype对象，</p>
			<pre>
				o.prototype = {};
			</pre>
			<p>那么，下一步必然是为新的prototype对象加上constructor属性，并将这个属性指回原来的构造函数。</p>
			<pre>
				　o.prototype.constructor = o;
			</pre>
		</details>
		<details>
			<summary> 直接继承prototype</summary>
			<p>第三种方法是对第二种方法的改进。由于Animal对象中，不变的属性都可以直接写入Animal.prototype。所以，我们也可以让Cat()跳过 Animal()，直接继承Animal.prototype。</p>
			<pre>
				更改后的animal
				function Animal(){ }
　				Animal.prototype.species = "动物";
			</pre>
			<p>然后，将Cat的prototype对象，然后指向Animal的prototype对象，这样就完成了继承。</p>
			<pre>
				Cat.prototype = Animal.prototype;
			　　	Cat.prototype.constructor = Cat;
			　　	var cat1 = new Cat("大毛","黄色");
			　　	alert(cat1.species); // 动物
			</pre>
			<p>与前一种方法相比，这样做的优点是效率比较高（不用执行和建立Animal的实例了），比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象，那么任何对Cat.prototype的修改，都会反映到Animal.prototype。	</p>
			<p>所以，上面这一段代码其实是有问题的。请看第二行</p>
			<pre>
				Cat.prototype.constructor = Cat;
			</pre>
			<p>这一句实际上把Animal.prototype对象的constructor属性也改掉了！</p>
			<pre>
				　alert(Animal.prototype.constructor); // Cat
			</pre>
		</details>
		<details>
			<summary>利用空对象作为中介</summary>
			<pre>
				var F = function(){};
			　	F.prototype = Animal.prototype;
			　　	Cat.prototype = new F();
			　　	Cat.prototype.constructor = Cat;
			</pre>
			<p>F是空对象，所以几乎不占内存。这时，修改Cat的prototype对象，就不会影响到Animal的prototype对象。</p>
			<pre>alert(Animal.prototype.constructor); // Animal</pre>
			<p>我们将上面的方法，封装成一个函数，便于使用。</p>
			<pre>
				function extend(Child, Parent) {
				　　　var F = function(){};
				　　　F.prototype = Parent.prototype;
				　　　Child.prototype = new F();
				　　　Child.prototype.constructor = Child;
				　　　Child.uber = Parent.prototype;
			　　}
			</pre>
			<p>使用时</p>
			<pre>
				　   extend(Cat,Animal);
				　　var cat1 = new Cat("大毛","黄色");
				　　alert(cat1.species); // 动物
			</pre>
			<p>另外，说明一点，函数体最后一行,意思是为子对象设一个uber属性，这个属性直接指向父对象的prototype属性。（uber是一个德语词，意思是"向上"、"上一层"。）这等于在子对象上打开一条通道，可以直接调用父对象的方法。这一行放在这里，只是为了实现继承的完备性，纯属备用性质。</p>
		</details>
		<details>
			<summary>拷贝继承</summary>
			<p>上面是采用prototype对象，实现继承。我们也可以换一种思路，纯粹采用"拷贝"方法实现继承。简单说，如果把父对象的所有属性和方法，拷贝进子对象，不也能够实现继承吗？这样我们就有了第五种方法。</p>
			<p>首先，还是把Animal的所有不变属性，都放到它的prototype对象上。</p>
			<pre>
				function Animal(){}
　　				Animal.prototype.species = "动物";
			</pre>
			<p>然后，再写一个函数，实现属性拷贝的目的。</p>
			<pre>
				function extend2(Child, Parent) {
			　　　　var p = Parent.prototype;
			　　　　var c = Child.prototype;
			　　　　for (var i in p) {
			　　　　　　c[i] = p[i];
			　　　　　　}
			　　　　c.uber = p;
			　　}
			</pre>
			<p>这个函数的作用，就是将父对象的prototype对象中的属性，一一拷贝给Child对象的prototype对象。</p>
			<p>使用的时候，这样写：</p>
			<pre>
				　extend2(Cat, Animal);
　　				 var cat1 = new Cat("大毛","黄色");
　　				 alert(cat1.species); // 动物
			</pre>
		</details>
		
	</body>
</html>
